package expression;

import java.util.HashMap;
import java.util.Stack;

public class Generator {

	// CONSTANTS

	private static final Fn[] FN = new Fn[]
	{ new FnBoolNOT(), new FnBoolAND(), new FnBoolOR(), new FnArithAdd(), new FnArithSubtract(), new FnArithDivide(), new FnArithMultiply(), new FnBoolGreaterEqual(),
		new FnBoolGreater(), new FnBoolSmaller(), new FnBoolSmallerEqual(), new FnBoolEqual(), new FnBoolNotEqual() };
	private static String STR_LT_PAR = "(", STR_RT_PAR = ")", STR_QUOTES = "\'\"";
	private static int LT_PAR = 0, RT_PAR = 1, OPERATOR = 2, CONSANT_STRING = 3, CONSANT_NUMBER = 4, CONSANT_BOOL = 5, VARIABLE = 6;

	//

	private int csr;
	private String in;
	private int type;

	public Generator(String infix) {
		reset(infix);
	}

	public void reset(String infix) {
		this.in = infix;
		csr = 0;
	}

	private boolean hasNext() {
		return csr < in.length();
	}

	public Fn generate() throws ExpressionException {
		Stack<String> operator = new Stack<String>();
		Stack<Fn> operand = new Stack<Fn>();
		// PARSING
		reset(in);
		while (hasNext()) {
			String token = next();
			// operand
			if (type == CONSANT_STRING)
				operand.add(new FnStringConstant(token));
			else if (type == CONSANT_NUMBER)
				operand.add(new FnNumberConstant(stringToNumber(token)));
			else if (type == CONSANT_BOOL)
				operand.add(new FnBoolConstant(stringToBool(token)));
			else if (type == VARIABLE)
				operand.add(new FnVariable(token));
			// operator
			else if (type == OPERATOR) {
				if (!operator.isEmpty() && !isLtParenthesis(operator.peek().charAt(0)) && comparePrecedence(operator.peek(), token) >= 0)
					push(operator.pop(), operand);
				operator.push(token);
			}
			// ltPar
			else if (type == LT_PAR)
				operator.push(token);
			// rtPar
			else if (type == RT_PAR) {
				String pop;
				while (!isLtParenthesis((pop = operator.pop()).charAt(0)))
					push(pop, operand);
			}
		}
		// BUILD TREE
		while (!operator.isEmpty()) {
			push(operator.pop(), operand);
		}
		if (operand.size() > 1)
			throw new ExpressionException();
		return operand.pop();
	}

	public static Fn generate(String infix) throws ExpressionException {
		return new Generator(infix).generate();
	}

	// FNS

	private static Fn push(String string, Stack<Fn> operand) throws ExpressionException {
		Fn fn = stringToFn(string);
		for (int i = 0; i < fn.getNParam(); i++)
			fn.addParam(operand.pop());
		operand.push(fn);
		return fn;
	}

	private static Number stringToNumber(String word) {
		try {
			return Integer.parseInt(word);
		} catch (Exception e) {}
		try {
			return Float.parseFloat(word);
		} catch (Exception e) {}
		try {
			return Double.parseDouble(word);
		} catch (Exception e) {}
		return null;
	}

	private static Boolean stringToBool(String word) {
		try {
			return Boolean.parseBoolean(word);
		} catch (Exception e) {}
		return null;
	}

	private static Fn stringToFn(String string) {
		for (Fn fn : FN)
			if (fn.symbol().trim().equalsIgnoreCase(string.trim()))
				try {
					return fn.getClass().newInstance();
				} catch (Exception e) {}
		return null;
	}

	private static int comparePrecedence(String operator1, String operator2) {
		return precedence(operator1) - precedence(operator2);
	}

	private static int precedence(String string) {
		for (Fn fn : FN)
			if (fn.symbol().trim().equalsIgnoreCase(string.trim()))
				try {
					return fn.precedence();
				} catch (Exception e) {}
		return -1;
	}

	// STRINGS

	private String next() {
		if(csr >= in.length())
			return "";
		char c = in.charAt(csr);
		String out = c + "";
		// FIRST CHARS
		if (isLtParenthesis(c))
			type = LT_PAR;
		else if (isRtParenthesis(c))
			type = RT_PAR;
		else if (isNumber(c))
			type = CONSANT_NUMBER;
		else if (isQuotes(c)) {
			type = CONSANT_STRING;
			out = "";
		} else if (isLetter(c)) {
			type = VARIABLE;
		} else if (isWhitespace(c)) {
			csr++;
			return next();
		} else
			type = OPERATOR;
		if (type == LT_PAR || type == RT_PAR) {
			csr++;
			return out;
		}
		// CONTINUE
		try {
			while (csr < in.length()) {
				c = in.charAt(++csr);
				// string
				if (type == CONSANT_STRING) {
					if (isQuotes(c)) {
						csr++;
						break;
					}
				}
				// variable or constant
				else if (type != OPERATOR) {
					if (type == CONSANT_NUMBER && !isNumber(c))
						break;
					else if (!isLetter(c) && !isNumber(c))
						break;
				}
				// operator
				else if (isWhitespace(c) || isNumber(c) || isLetter(c) || isLtParenthesis(c) || isRtParenthesis(c)) {
					break;
				}
				out += c;
			}
		} catch (IndexOutOfBoundsException e) {}
		if (out.trim().length() == 0)
			return next();
		if (type == VARIABLE && isBoolConst(out))
			type = CONSANT_BOOL;
		return out;
	}

	private boolean isBoolConst(String in) {
		return in.equalsIgnoreCase("true") || in.equalsIgnoreCase("false");
	}

	private static boolean isWhitespace(char c) {
		return Character.isWhitespace(c);
	}

	private static boolean isLtParenthesis(char c) {
		return STR_LT_PAR.indexOf(c) > -1;
	}

	private static boolean isRtParenthesis(char c) {
		return STR_RT_PAR.indexOf(c) > -1;
	}

	private static boolean isQuotes(char c) {
		return STR_QUOTES.indexOf(c) > -1;
	}

	private static boolean isNumber(char c) {
		return Character.isDigit(c) || c == '.';
	}

	private static boolean isLetter(char c) {
		return Character.isLetter(c) || c == '_';
	}

	// MAIN

	public static void main(String[] args) throws Exception {
		// tree
		Fn fn = new Generator("'new'").generate();
		System.out.println(fn);
		// value
		HashMap<String, Object> map = fn.getVariableMap();
		map.put("C1", 2);
		System.out.println(map);
		System.out.println(fn.evaluate(map));
	}
}
